/*
 *	----------YuBingDoms----------
 * 	
 *   author : YuBing  羽冰
 *   ver    : 1.1.0
 *   time   : 2020.11.12
 *   change : 
 * 
 *   下次修改:多属性运动的封装，太难看了，看一下能不能精简修改一下
 *           顺便可以添加个传入运动处理函数进去，就是运动到某一时刻可以处理点东西
 * 
 *           把构造函数的功能增加一些，多增加个传入dom对象数组也能使用的功能
 *          现在只能传入字符串、单个dom对象
*/

; (function (window, document) {
    /* 无new调用，需要两个构造函数 */
    var YuBingUtils = function (dom) {
        /* 第一个构造函数，返回真正构造函数创建好的对象 */
        return new YuBingUtils.prototype.Constructor(dom);
    }
    /* 对dom的操作放在原型对象里 */
    YuBingUtils.prototype = {
        /* 第二个构造函数，负责创建对象 */
        Constructor: function (dom) {
            if (typeof dom === "string") {
                /* 输入字符串，说明要求选中dom */
                var eles = document.querySelectorAll(dom);
                /* 2. 把所有的元素放入到 实例对象之中; */
                for (var i = 0; i < eles.length; i++) {
                    this[i] = eles[i];
                }
                /* 3. 给实例对象添加length属性; */
                this.length = eles.length;
            }
            /* 传入的是dom
             * 单个元素节点或者是document节点，只有单个元素才能使用nodeType，因此可以用这个方式判定是不是单个元素 */
            if (dom.nodeType === 1 || dom.nodeType === 9) {
                this[0] = dom;
                this.length = 1;
            }
        },
        /* 样式操作类(1):   
         *   参数设置 : 
         *  cssStyle : 样式,传入对象设置dom行内样式，传入数组获取dom样式设置
         *              1.对dom添加行内样式设置,格式 : 
         *              {
         *                  "css" : "value",
         *                  "css" : "value",
         *                  ····
         *              }
         *              2.获取dom样式设置，格式 : 
         *                  ["left","top"···]
         *  注意事项 : 设置样式时要求自带单位
         *             获取样式值只适用于单数值类型样式，像 XXX: 0 0 0或XXX：red 的不行
         *             获取样式值只能获取一个元素样式值，如果是一组dom就只能获取第一个dom的
         * 本类适用于js常设置的css样式，个别参数请在css样式表手动设置 
        */
        css: function (cssStyle) {
            if (typeof cssStyle === "undefined") throw "参数错误, css必须传递参数";
            // 传入对象,设置样式
            if (YuBingUtils.isObject(cssStyle)) {
                for (var i = 0; i < this.length; i++) {
                    if (typeof Object.assign === "function") {
                        Object.assign(this[i].style, cssStyle);
                    } else {
                        for (var attr in cssStyle) {
                            this[i].style[attr] = cssStyle[attr];
                        }
                    }
                }
                return this;
            }
            // 传入样式数组,返回样式设置
            var attr_list = {};
            for (var i = 0; i < cssStyle.length; i++) {
                var attr_value = parseFloat(getComputedStyle(this[0])[cssStyle[i]]);
                switch (cssStyle[i]) {
                    case "left":
                        attr_list[cssStyle[i]] = attr_value ? attr_value : this[0].offsetLeft;
                        break;
                    case "top":
                        attr_list[cssStyle[i]] = attr_value ? attr_value : this[0].offsetTop;
                        break;
                    default:
                        attr_list[cssStyle[i]] = attr_value;
                }
            }
            return attr_list;
        },

        /* 类名辨别类
         * 辨别指定dom的className中是否含有指定class 
         * 参数设置 : 
         *    className : 要检测的目标类名,如果传入数组就分别判别数组中所有的类名
         *    注意事项   : dom可以是单数也可以是复数
         *     返回值    : 如果存在类名，返回key为类名，value为含有该类名的dom的数组 
         *                 组成的数组对象 :
         *                  {
         *                      className : [dom,dom,dom··]
         *                  }
         *                 如果不存在，返回false
        */
        hasClass: function (className) {
            if (!className) throw "className can't be null";
            // 用于存入含有指定类名的dom及其指定类名
            var hasClassDoms = {};
            var class_arr;
            if (!YuBingUtils.isArray(className)) {
                // 如果传入一个类名
                class_arr = []
                class_arr.push(className);
            } else {
                class_arr = className;
            }
            for (var j = 0; j < this.length; j++) {
                var dom = this[j];
                // 获取类名
                var className_string = dom.className;
                if (typeof "".indexOf === "function") {
                    // 高版本支持indexOf
                    class_arr.forEach(function (item) {
                        if (className_string.indexOf(item) >= 0) {
                            // 如果包含类名
                            if (!hasClassDoms[item]) {
                                // 如果之前没有这个key
                                var arr = [];
                                arr.push(dom);
                                hasClassDoms[item] = arr;
                            } else {
                                hasClassDoms[item].push(dom);
                            }
                        }
                    })
                } else {
                    // 低版本不支持indexOf
                    for (var i = 0; i < class_arr.length; i++) {
                        var reg = new RegExp(class_arr[i], "g");
                        if (reg.test(className_string)) {
                            // 如果包含类名
                            if (!hasClassDoms[class_arr[i]]) {
                                // 如果之前没有这个key
                                var arr = [];
                                arr.push(dom);
                                hasClassDoms[class_arr[i]] = arr;
                            } else {
                                hasClassDoms[class_arr[i]].push(dom);
                            }
                        }
                    }
                };
            }
            // 如果是空对象，返回false，不是空对象，返回对象
            return !(YuBingUtils.isEmpty(hasClassDoms)) ? hasClassDoms : false;
        },

        /* 类名添加类
         * 向指定dom的className中添加指定className 
         *  参数设置  : 
         * className : 要设置的类名,可以传入一个，也可以以数组的形式传入一组
        */
        addClass: function (className) {
            if (!className) {
                throw "className can't be null";
            }
            var class_arr;
            if (!YuBingUtils.isArray(className)) {
                // 如果传入一个类名
                class_arr = []
                class_arr.push(className);
            } else {
                class_arr = className;
            }
            for (var i = 0; i < this.length; i++) {
                var dom = this[i];
                if (typeof Set === "function") {
                    // 如果支持Set
                    class_arr = dom.className.split(/\s+/).concat(class_arr);
                    // 去重
                    class_arr = Array.from(new Set(class_arr));
                    // 连接成字符串之后去掉两头的空格字符再设置
                    dom.className = class_arr.join(" ").trim();
                } else {
                    // 如果是低版本不支持Set
                    /* 这里只能单个dom调用，而不能用this调用
                    this调用就会把所有dom传入，这时一旦有一个dom有对应类名
                    其他的dom就没法设置了 */
                    var classes = yu(dom).hasClass(class_arr);
                    var class_copy = class_arr;
                    if (classes) {
                        // 如果有重复值
                        for (var attr in classes) {
                            class_copy = class_copy.join(" ").split(attr);
                        }
                    }
                    class_copy = class_copy.join(" ").trim();
                    dom.className += " " + class_copy;
                }
            }
            return this;
        },

        /* 类名移除类
         * 将指定的类名从dom的className中移除 
         *   参数设置  : 
         *  className : 要移除的类名，可以传入一个也可以以数组的形式传入一组
        */
        removeClass: function (className) {
            if (!className) throw "className can't be null";
            var class_arr;
            if (!YuBingUtils.isArray(className)) {
                // 如果传入一个类名
                class_arr = []
                class_arr.push(className);
            } else {
                class_arr = className;
            }
            for (var i = 0; i < this.length; i++) {
                var dom = this[i];
                if (typeof [].reduce) {
                    // 高版本支持reduce
                    var classArr = dom.className.split(/\s+/);
                    classArr = classArr.reduce((value, item) => {
                        if (class_arr.indexOf(item) < 0) value.push(item);
                        return value;
                    }, []);
                    dom.className = classArr.join(" ").trim();
                } else {
                    // 低版本不支持reduce
                    var className_string = dom.className;
                    for (var j = 0; j < class_arr.length; j++) {
                        className_string = className_string.split(class_arr[j]).join("");
                    }
                    dom.className = className_string;
                }
            }
            return this;
        },
        /* 切换使用移除类名和添加类名
         * 如果有指定类名   -->移除
         * 如果没有指定类名  -->添加 
         *   参数设置 : 
         * className : 要进行切换的类名，可以是一个，也可以以数组的形式传入一组
        */
        toggleClass: function (className) {
            if (!className) throw "className can't be null";
            var class_arr;
            if (!YuBingUtils.isArray(className)) {
                // 如果传入一个类名
                class_arr = []
                class_arr.push(className);
            } else {
                class_arr = className;
            }
            if (this.hasClass(class_arr)) {
                // 如果存在指定类就移除
                this.removeClass(class_arr);
                return this;
            }
            // 否则添加
            this.addClass(class_arr);
            return this;
        },

        /* 
         * 找到一组dom中的单个使用的;
         * 参数设置：
         *   index : 数组下标
         *   type  : 类型，默认array，即返回单个元素数组，  object 直接返回选中的dom元素 
        */
        oneOf: function (index, type) {
            type = type || "array";
            if (type == "array") {
                return { 0: this[index], length: 1, __proto__: YuBingUtils.prototype, prev: this };
            } else {
                return this[index];
            }
        },
        /* 移除指定节点
         * 参数设置:
         *     start : 开始位置下标，移除下标对应元素（包括它自己）开始直到结束位置前一个的元素
         *     end   : 结束位置下标 
         *  注意事项  ： 不输入参数则默认移除所有选中元素
         */
        remove: function (start, end) {
            start = start || 0;
            end = end || this.length;
            for (var i = start; i < end; i++) {
                this[i].parentNode.removeChild(this[i]);
            }
            return this;
        },
        // 给元素添加事件监听/事件委托
        /* 参数设置:
         * type:事件类型，格式： "click","mousedown"···
         * selector: 选中的元素的子级
         * func:事件处理函数
         */
        onEvent: function (type, selector, func) {
            for (var i = 0; i < this.length; i++) {
                // 事件委托
                // cao，低版本浏览器做个P的事件委托，一堆api不兼容，IE，永远滴神，cao
                if (arguments.length === 3) {
                    // 使用闭包达成批量事件委托的目的
                    (function (that) {
                        var index = i;
                        that[index].addEventListener(type, function (evt) {
                            var eles;
                            if (typeof selector == "string") {
                                // 传入字符串则进行查找选择
                                eles = that[index].querySelectorAll(selector);
                                // 将伪数组转为真数组
                                if (eles) eles = [].slice.call(eles);
                                else throw "Can't set EventListener on null/undefined";
                            }
                            else if (selector.nodeType === 1 || selector.nodeType === 9) {
                                // 传入dom元素
                                eles = selector;
                            }
                            else if (YuBingUtils.isArray(selector)) {
                                // 传入dom数组
                                selector.forEach(function (item) {
                                    if (!(item.nodeType === 1 || item.nodeType === 9)) {
                                        // 进行验证，如果传入的不是dom元素数组或数组包含非dom元素就报错
                                        throw "selector must be domObject or Array of domObject";
                                    }

                                    var isChild = true;
                                    var parent = item;
                                    // 查看委托目标是否是其子元素
                                    while (item != that[index] && parent != that[index]) {
                                        parent = parent.parentNode;
                                        if (parent == document.body) {
                                            isChild = false;
                                            break;
                                        }
                                    }
                                    if (!isChild) {
                                        // 进行验证，dom元素数组中的内容必须是子元素
                                        throw "selector must be child of the one whitch you want to set eventListener";
                                    }
                                });
                                eles = selector;
                            } else {
                                throw "selector must be domObject or Array of domObject";
                            }
                            var e = evt || event;
                            var target = e.target || e.srcElement;
                            // 事件委托，让事件冒泡到委托对象层面，然后触发事件
                            while (target != that[index] && target) {
                                if (eles.indexOf(target) !== -1) {
                                    func.call(target, e);
                                    break;
                                }
                                target = target.parentNode;
                            }
                        });
                    })(this);
                } else {
                    var dom = this[i];
                    // 添加事件 :
                    // 把 func 赋值为 selector ;
                    func = selector;
                    if (typeof dom.addEventListener === "function") {
                        // 事件监听
                        dom.addEventListener(type, func);
                    }
                    else if (typeof dom.attachEvent === "function") {
                        // IE兼容
                        dom.attachEvent("on" + type, func);
                    }
                    else {
                        // 事件绑定
                        dom["on" + type] = func;
                    }
                }
            }
            return this;
        },
        // 给指定dom元素取消事件
        /* 参数设置:
         * type : 事件类型,格式： "click","mousedown"···
         * func : 事件处理函数
        */
        offEvent: function (type, func) {
            for (var i = 0; i < this.length; i++) {
                var dom = this[i];
                if (typeof dom.removeEventListener === "function") {
                    // 移除事件监听
                    dom.removeEventListener(type, func);
                }
                else if (typeof dom.detachEvent === "function") {
                    // IE兼容
                    dom.detachEvent("on" + type, func);
                }
                else {
                    // 取消事件绑定
                    dom["on" + type] = null;
                }
            }
            return this;
        },
        /* dom多属性匀速/缓冲运动动画
         *
         *  参数设置:
         *    dom       : 操作对象
         *  cssStyleObj : 需要变化的css样式及其最终值
         *     格式      ：
         * {
         *     "cssStyle"：endValue,
         *     "cssStyle": endValue,
         *     ···· 
         * }
         *    type     : 运动的类型, 默认为false 即匀速运动，输入true变为缓冲运动
         *    time     : 匀速运动时间/变速运动分母, 匀速默认2秒，变速默认20
         * 其他注意事项 : 传入的值需要设置position属性 
         */
        sportAni: function (cssStyleObj, time, type) {
            // 运动类型
            type = type || false;
            // 匀速运动时间/变速运动分母, 匀速默认2秒，变速默认20
            time = type == "uniform" ? time || 2 : time || 20;
            // 属性值，包含起始值、终值、差值、速度、当前值
            var value = {};
            // 执行次数,计时器运行次数,匀速运动专用，默认的话，就是等于100
            var perform_count = time * 1000 / 20
            // 传入的属性数量
            var style_count = 0;
            // 起始值
            var start_value;
            // 终值
            var end_value;
            // 差值
            var different_value;
            // 变化速度
            var speed_value;

            // 最基本的参数错误检测 
            if (typeof cssStyleObj !== "object" || !(cssStyleObj instanceof Object)) {
                throw "样式参数错误：请输入对象";
            }
            if (typeof type !== "boolean") {
                throw "输入运动类型错误：false 匀速运动，true 缓冲运动"
            }
            if (time <= 0) throw "time参数错误:只能大于0";

            for (var i = 0; i < this.length; i++) {
                var dom = this[i];
                // 获取属性数量、初始值、终止值、当前值、差值、速度、该属性是否已经结束动画（防止属性结束动画后对style_count的干扰）
                for (var count in cssStyleObj) {
                    style_count++;
                    // 透明度没有px单位而别的有，在获取值的时候就很蛋疼，所以只能分开计算
                    if (count == "opacity") {
                        if (cssStyleObj[count] < 0) {
                            throw "透明度值有误";
                        }
                        start_value = getComputedStyle(dom)[count] * 100;
                        end_value = cssStyleObj[count] * 100;
                        different_value = end_value - start_value;
                        speed_value = different_value / perform_count > 0 ? Math.ceil(different_value / perform_count) : Math.floor(different_value / perform_count);
                    } else {
                        start_value = parseInt(getComputedStyle(dom)[count]);
                        end_value = cssStyleObj[count];
                        different_value = end_value - start_value;
                        speed_value = different_value / perform_count > 0 ? Math.ceil(different_value / perform_count) : Math.floor(different_value / perform_count);
                    }

                    value[count] = {
                        start: start_value,
                        end: end_value,
                        different: different_value,
                        speed: speed_value,
                        now: start_value,
                        isready: false
                    }
                }

                clearInterval(dom.t);
                if (!type) {
                    // 匀速运动
                    dom.t = setInterval(function () {
                        for (var style in value) {
                            value[style].now += value[style].speed;
                            value[style].different = value[style].end - value[style].now;

                            // 设置值
                            if (style == "opacity") {
                                // 距离小于速度，直接一脚过去，送你一程直接到终点，nice
                                if (Math.abs(value[style].different) <= Math.abs(value[style].speed)) {
                                    value[style].now = value[style].end;
                                    value[style].different = 0;
                                    dom.style[style] = (value[style].end / 100).toFixed(1);
                                } else {
                                    dom.style[style] = (value[style].now / 100).toFixed(1);
                                }
                            } else {
                                if (Math.abs(value[style].different) <= Math.abs(value[style].speed)) {
                                    value[style].now = value[style].end;
                                    value[style].different = 0;
                                    dom.style[style] = value[style].end + "px";
                                } else {
                                    dom.style[style] = value[style].now + "px";
                                }
                            }

                            // 终止条件
                            if (value[style].now == value[style].end && !(value[style].isready)) {
                                // console.log("end", style_count)
                                value[style].isready = true;
                                --style_count;
                                if (style_count == 0) {
                                    clearInterval(dom.t);
                                }
                            }
                        }
                    }, 20);

                }
                else {
                    // 缓冲运动
                    sportAni_t = setInterval(function () {
                        for (var style in value) {
                            /* 
                             * 变速   
                             * 用now参与计算speed是不对的,一方面如果是缩小，
                             * now变为0的时候speed就会出问题，这个* 没办法 
                            */
                            /* 分母只能用固定值 */
                            speed_value = value[style].different / time > 0 ? Math.ceil(value[style].different / time) : Math.floor(value[style].different / time);

                            value[style].speed = speed_value;
                            value[style].now += value[style].speed;
                            value[style].different = value[style].end - value[style].now;

                            /* 设置值 */
                            if (style == "opacity") {
                                /* 距离小于速度，直接一脚过去，送你一程直接到终点，nice */
                                if (Math.abs(value[style].different) <= Math.abs(value[style].speed)) {
                                    value[style].now = value[style].end;
                                    value[style].different = 0;
                                    dom.style[style] = (value[style].end / 100).toFixed(1);
                                } else {
                                    dom.style[style] = (value[style].now / 100).toFixed(1);
                                }
                            } else {
                                if (Math.abs(value[style].different) <= Math.abs(value[style].speed)) {
                                    value[style].now = value[style].end;
                                    value[style].different = 0;
                                    dom.style[style] = value[style].end + "px";
                                } else {
                                    dom.style[style] = value[style].now + "px";
                                }
                            }

                            /* 终止条件 */
                            if (value[style].now == value[style].end && !(value[style].isready)) {
                                value[style].isready = true;
                                --style_count;
                                if (style_count == 0) {
                                    clearInterval(sportAni_t);
                                }
                            }
                        }
                    }, 20);
                }
            }
            return this;
        },
    }

    /* 创建dom对象并设置css样式 
     * 参数设置:
     *    domType : 创建的dom对象的类型，例如可以传入 "div" 则创建div类型的dom
     *   cssStyle : 要给创建的dom设置的css样式，不传入则不设置
     *      样式格式： {
     *                  cssstyle: value,
     *                  cssstyle: value,
     *                      `````             
     *                  }
     *      样式名要正确，且value必须格式正确，有单位的要带单位
     *    parent  : 父级元素
    */
    YuBingUtils.createEle = function (domType, cssStyle, parent) {
        var dom = document.createElement(domType);
        if (cssStyle) yu(dom).css(cssStyle);
        if (parent) parent.appendChild(dom);
        return dom;
    }

    /* 样式操作类 (2)
 *   对内联样式表设置样式
 *   表内有目标样式，就往里添加样式
 *   表内没有设置目标样式，就创建一个
 *  (不太适合加入上面的dom系列操作，因为selector必须是string,而上面的操作是具体的dom)
 *     参数设置    : 
 *    selector    : css表中能选中目标dom的标签,比是strinng --> css表中,花括号前的选择符
 *   styleObject  : 要添加的样式
 *                     格式:{
 *                              "cssStyle" : value,
 *                              "cssStrle" : value,
 *                                  ···
 *                          }
 *      注意事项  : 可以用，但是没必要，静态部分直接写css
 *                  动态部分用上面的dom操作类设置就好
 *                  这里的addRule还是被废弃的标签
*/
    YuBingUtils.setCss = function (selector, styleObject) {
        if (typeof selector != "string") throw "selector's type must be string";
        // 如果没有样式表
        if (document.styleSheets.length === 0) yu.createEle("style", undefined, document.head);
        // document.styleSheets -->返回所有style标签的集合的数组
        // document.styleSheets[document.styleSheets.length - 1] ↓
        // ↑ 获取返回的style标签集合数组中的最后一个style标签及其里边的内容
        var styleSheet = document.styleSheets[document.styleSheets.length - 1];
        var css = "";
        for (var prop in styleObject) {
            var value = styleObject[prop]
            prop = prop.replace(/[A-Z]/g, function (s) {
                return "-" + s.toLowerCase();
            })
            css += prop + ":" + value + ";";
        }
        // 这里实际插入是直接在css树中，因此不会在html结构中显示
        if (styleSheet.insertRule) styleSheet.insertRule(selector + "{" + css + "}", styleSheet.cssRules.length);
        else styleSheet.addRule(selector, css, styleSheet.cssRules.length);
    },

        YuBingUtils.prototype.Constructor.prototype = YuBingUtils.prototype;
    /* 给YuBingUtils 别的几个名字，防止命名空间命名冲突时调用不了
     * 此时，在全局使用yu（），相当于调用YuBingUtils的第一个构造函数
     * 然后第一个构造函数返回第二个构造函数创建的对象 */
    window.yu = window.YuBingUtils = YuBingUtils;
})(window, document);